﻿#include "protohash.h"
#include <QDebug>

class ProtoUtil{
public:
    QByteArray buf;
    int pos = 0;
    int err = 0;
    ProtoUtil(const QByteArray& data):buf(data){}
    ProtoUtil(int size = 0)
    {
        if(size > 0)
            buf.reserve(size);
    }
    int decodeSize()
    {
        if (pos+1 > buf.length()){
            err++;
            return -1;
        }

        int val = 0;
        if (buf[pos] == char(0xFF)){
            if (pos+5 > buf.length()){
                err++;
                return -1;
            }

            pos++;
            val += (0x000000FF)&buf[pos++];
            val = val << 8;
            val += (0x000000FF)&buf[pos++];
            val = val << 8;
            val += (0x000000FF)&buf[pos++];
            val = val << 8;
            val += (0x000000FF)&buf[pos++];
            return val;
        }
        val = 0xff&buf[pos++];
        return val;
    }
    QByteArray decodeBin(int len)
    {
        if (pos + len > buf.length()) {
            err++;
            return QByteArray();
        }
        QByteArray bin = buf.mid(pos, len);
        pos += len;
        return bin;
    }
    int decodeHash(ProtoHash &hash)
    {
        int cnt = decodeSize();
        for (int i=0; i<cnt; i++){
            int name = decodeSize();
            if (err > 0)
                return -1;
            int len = decodeSize();
            if (err > 0)
                return -1;
            QByteArray val = decodeBin(len);
            if (err > 0)
                return -1;
            hash.setParam(name, val);
        }
        return pos;
    }
    void checkSize(int size)
    {
        if(buf.size() < size)
            buf.resize(size);
    }
    int encodeSize(int size)
    {
        if (size < 0xFF)  {
            checkSize(pos + 1);
            buf[pos++] = char(size&0xFF);
            return 1;
        }
        checkSize(pos + 5);
        buf[pos++] = char(0xFF);
        buf[pos++] = char(size>>24 & 0xFF);
        buf[pos++] = char(size>>16 &0xFF);
        buf[pos++] = char(size>>8&0xFF);
        buf[pos++] = char(size&0xFF);
        return 5;
    }
    int encodeBin(const QByteArray &data, int datapos, int len){
        if (datapos+len > data.length()){
            err++;
            return -1;
        }
        if (data.isEmpty())
            return 0;
        checkSize(pos + len);
        buf.insert(pos, data.constData() + datapos, len);
        pos += len;
        return len;
    }
    void encodeHash(const QVariantHash &hash)
    {
        encodeSize(hash.size());
        for(auto it = hash.constBegin(); it != hash.constEnd(); it++)
        {
            encodeSize(it.key().toInt());
            const QVariant &v = it.value();
            QByteArray val;
            if(v.userType() == QMetaType::QByteArray)
                val = v.toByteArray();
            else
                val = v.toString().toUtf8();
            encodeSize(val.length());
            encodeBin(val, 0, val.length());
        }
    }
};

ProtoHash::ProtoHash()
{
}

ProtoHash::ProtoHash(const QVariantHash &variantHash) : hash(variantHash)
{
}

QVariantHash ProtoHash::decodeHash(const QByteArray &buf)
{
    ProtoHash hash;
    if(hash.decode(buf) < 0)
        return QVariantHash();
    return hash.hash;
}

int ProtoHash::decode(const QByteArray &buf)
{
    ProtoUtil util(buf);
    return util.decodeHash(*this);
}

QByteArray ProtoHash::encode() const
{
    ProtoUtil util;
    util.encodeHash(hash);
    return util.buf.left(util.pos);
}

void ProtoHash::setParam(int key, const QVariant &variant)
{
    hash[QString::number(key)] = variant;
}

void ProtoHash::clear()
{
    hash.clear();
}

QDebug operator<<(QDebug debug, const ProtoHash &hash)
{
    return operator<<(debug, hash.hash);
}

QList<QVariantHash> ProtoArray::decodeArray(const QByteArray &buf)
{
    ProtoArray array;
    if(array.decode(buf) < 0)
        return QList<QVariantHash>();
    return array.list;
}

int ProtoArray::decode(const QByteArray &buf)
{
    ProtoUtil util(buf);
    int hashcnt = util.decodeSize();
    for (int j=0; j<hashcnt; j++){
        ProtoHash hash;
        if(util.decodeHash(hash) < 0)
            return -1;
        list.append(QVariantHash());
        list.last().swap(hash.hash);
    }
    return util.pos;
}

QByteArray ProtoArray::encode() const
{
    ProtoUtil util;
    util.encodeSize(list.size());
    for(auto it = list.constBegin(); it != list.constEnd(); it++)
    {
        ProtoHash hash;
        hash.hash = *it;
        util.encodeHash(hash.hash);
    }
    return util.buf.left(util.pos);
}

QVariantList ProtoArray::toVariantList() const
{
    QVariantList vl;
    for(auto const& v : list)
    {
        vl.append(v);
    }
    return vl;
}

void ProtoArray::clear()
{
    list.clear();
}

QDebug operator<<(QDebug debug, const ProtoArray &array)
{
    return operator<<(debug, array.list);
}
